package flightlifter;

import GUI.AbsoluteLayout;
import GUI.Button;
import GUI.GUI;
import GUI.GUIEvent;
import GUI.GUILayout;
import GUI.GUIListener;
import GUI.Menu;
import GUI.VerticalLayout;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Controller;
import org.lwjgl.input.Controllers;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.openal.AL10;
import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.Color;
import org.lwjgl.util.WaveData;
import org.lwjgl.util.glu.MipMap;
import org.lwjgl.util.glu.Sphere;

public class FlightLifter implements GUIListener{

    private boolean done = false;
    private boolean fullscreen = false;
    private boolean f1 = false;
    private boolean f2 = false;
    private boolean space = false;
    private boolean shift = false;
    private boolean ctrl = false;
    private boolean ekey = false;
    private boolean wkey = false;
    private boolean rkey = false;
    private boolean zkey = false;
    //available mouse modes
    private static final int POINT = 0;
    private static final int CONNECT = 1;
    private static final int SELECT = 2;
    private static final int CREATE_LINE = 3;
    private static final int CREATE_MIDPOINT = 4;
    private static final int NOT_USED = 5;
    private static final int SELECT_LINE = 6;
    private static final int CREATE_CONTROLLER = 7;
    private static final int CREATE_WING = 8;
    private static final int CREATE_ENGINE = 9;
    private static final int ADJUST_MASS = 10;
    private static final int CONNECT_WIRES = 11;
    private static final int CREATE_CAM = 12;
    private static final int CREATE_CANNON = 13;
    private static final int MOVE_POINTS = 14;
    private static final int CREATE_SMOKE = 15;
    private static final int CAPTURE_BUTTON = 16;
    private static final int SELECT_WING = 17;
    private static final int GRAB_TEXTURE = 18;
    private static final int CREATE_COUPLING = 19;
    private static final int CREATE_EXPLOSIVE = 20;
    private static final int CREATE_PROXIMITY_OBJECTIVE = 21;
    private static final int CREATE_AVIONICS = 22;
    private static final int CREATE_WHEEL = 23;
    private static final int BOX_SELECT = 24;
    private static final int EXTRUDE = 25;
    private static final int SCALE = 26;
    private static final int CAPTURE_AXIS = 27;
    //simulation constant
    public static final int CONSTRAINT_ITERATIONS = 50;
    public static final int FRAMERATE = 60;
    public static final int FRAMERATE_SQ = FRAMERATE * FRAMERATE;
    public static final int ZDIST = 150;
    public static final int SIZE_FLOAT = 4;
    public static final float WORLD_RADIUS = 20000;      //10Km
    //available view modes
    private static final int QUAD_VIEW = 1;
    private static final int PERSPECTIVE_VIEW = 2;
    private static final int WIRING = 3;
    private static final int FLIGHT_CAM = 4;


    //public rendering modes
    public static final int QUAD_RENDER = 0;
    public static final int SINGLE_RENDER = 1;

    private int mode = POINT;
    private float xPlaneDepth = 0;
    private float yPlaneDepth = 0;
    private float zPlaneDepth = 0;
    private float snapSize = 1;
    private DisplayMode displayMode;
    Controller playerControl = null;
    private Vector<Node> nodes = new Vector();
    private Vector<Node> constraints = new Vector();
    private Vector<WiringNode> wiring = new Vector();
    private Vector<Node> toAdd = new Vector();
    private Vector<Node> toRemove = new Vector();
    private int windowWidth = 1280;
    private int windowHeight = 1024;
    private int screenWidth = 1280;
    private int screenHeight = 1024;
    private boolean mouseDown = false;
    private int running = 0;
    private OrthoCamera topCam, rightCam, frontCam;
    private PerspectiveCamera perCam, chaseCam;
    private Point last = null;
    private Camera activeWindow;
    TrueTypeFont trueTypeFont;
    //TODO: abstract these out
    TextureLoader texLoader = new TextureLoader();
    Texture tex, wingTex;
    // Light position: if last value is 0, then this describes light direction.  If 1, then light position.
    float lightPosition0[] = {0f, 0f, 100f, 1f};
    Vector<Selectable> selected = new Vector();
    WiringNode selectedWireNode = null;
    boolean joystickActive = false;
    private static ByteBuffer fBuf = BufferUtils.createByteBuffer(100);
    static ByteBuffer buffer = BufferUtils.createByteBuffer(1024);
    //SHADERS
    private int basicShader;
    private int shadowShader;
    private int textureShader;
    private int toonShader;
    private int viewMode = PERSPECTIVE_VIEW;
    private boolean shadows = false;
    private boolean jkey = false;
    private FlightCam flightCam = null;
    //GUI memers
    private GUI gui, wiringGUI;
    private VerticalLayout dataBlock;
    private AbsoluteLayout wiringLayout;
    private VerticalLayout probeMenu;
    private VerticalLayout infoBox;
    private Button button;
    private int mouseX = 0;
    private int mouseY = 0;
    private int lastMouseX = 0;
    private int lastMouseY = 0;
    private Vector<String> undoBuffer = new Vector();
    MeshLoader meshLoader = new MeshLoader();
    MeshLoader engineMesh = new MeshLoader();
    MeshLoader wheelMesh = new MeshLoader();
    MeshLoader cannonMesh = new MeshLoader();
    MeshLoader fanBlades = new MeshLoader();
    MeshLoader cloud = new MeshLoader();
    //Sound variables go here
    IntBuffer soundBuffer = BufferUtils.createIntBuffer(1);
    IntBuffer source = BufferUtils.createIntBuffer(1);
    FloatBuffer sourcePos = BufferUtils.createFloatBuffer(3).put(new float[]{0.0f, 0.0f, 0.0f});
    FloatBuffer sourceVel = BufferUtils.createFloatBuffer(3).put(new float[]{0.0f, 0.0f, 0.0f});
    FloatBuffer listenerPos = BufferUtils.createFloatBuffer(3).put(new float[]{0.0f, 0.0f, 0.0f});
    FloatBuffer listenerVel = BufferUtils.createFloatBuffer(3).put(new float[]{0.0f, 0.0f, 0.0f});
    /** Orientation of the listener. (first 3 elements are "at", second 3 are "up") */
    FloatBuffer listenerOri =
            BufferUtils.createFloatBuffer(6).put(new float[]{0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f});
    String debug = "------Console------\n";
    Cloud[] cloudLayer;
    private int numClouds = 100;
    int lMouseX = -1, lMouseY = -1;
    private TerrainChunk ground = new TerrainChunk();
    public static FloatBuffer tmpFloats = allocFloats(4);          // temp var used by setLightPos(), setFog()
    float count = 0;

    PVector scaleCenter;

    public static void main(String args[]) {
        boolean fullscreen = false;

        FlightLifter fl = new FlightLifter();
        fl.run(fullscreen, 1280, 1024);
    }

    public void run(boolean fullscreen, int width, int height) {

        this.windowWidth = width;
        this.windowHeight = height;
        screenWidth = width;
        screenHeight = height;
        this.fullscreen = fullscreen;


        try {
            createWindow();
            initGL();
            initGUI();
            initControllers();

            initCameras();
            Mouse.create();

            while (!done) {

                pollMouse();
                handleKeyboard();

                if (running == 1) {
                    physics();
                    handleWiring();
                }
                //camera handling
                GL11.glEnable(GL11.GL_DEPTH_TEST);
                GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);      // Clear The Screen And The Depth Buffer
                GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);



                shadows = false;

                if (viewMode == QUAD_VIEW) {
                    renderQuad();
                } else {
                    renderSingle();

                    GL20.glUseProgram(0);

                    if (viewMode == WIRING) {
                        //renderWiring();
                        wiringGUI.draw();
                    }

                }

                GL11.glDisable(GL11.GL_DEPTH_TEST);
                gui.draw();
                renderMouseHelp();
                trueTypeFont.drawString(800, height - 50, debug, 1, 1);



                Display.update();

                if (mode == CAPTURE_BUTTON) {
                    for (int i = 0; i < playerControl.getButtonCount(); i++) {
                        if (playerControl.isButtonPressed(i)) {
                            WiringNode temp = new JoystickButton(i, playerControl, trueTypeFont, selected, 100, 100);
                            wiringLayout.add(temp);
                            wiring.add(temp);
                            mode = SELECT;
                            break;
                        }
                    }


                } else if (mode == CAPTURE_AXIS) {
                    for (int i = 0; i < playerControl.getAxisCount(); i++) {
                        if (playerControl.getAxisValue(i) == 1 ||
                             playerControl.getAxisValue(i) == -1   ) {
                            WiringNode temp = new JoystickAxis(i, playerControl, trueTypeFont, selected, 100, 100);
                            wiringLayout.add(temp);
                            wiring.add(temp);
                            mode = SELECT;
                            break;
                        }
                    }


                }


                else if (mode == CONNECT_WIRES) {
                    if (selected.size() == 2) {

                        pushUndo();
                        //check to make sur we connect an output to an input
                        if ((selected.elementAt(0) instanceof WiringOutput)
                                && (selected.elementAt(1) instanceof WiringConsumer)) {
                            ((WiringOutput) selected.elementAt(0)).setConsumer((WiringConsumer) selected.elementAt(1));
                        }
                        clearSelected();
                    }
                }



            }
            cleanup();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

    private void renderSingle() {
        shadows = true;
        if (running == 1) {
            count += .01f;
        }
        if (viewMode == FLIGHT_CAM) {
            flightCam.camera();
        } else {
            chaseCam.camera();
        }

        lights();

        //draw the ground
        GL20.glUseProgram(toonShader);
        tex.bind();

        GL11.glPushMatrix();

        GL11.glColor4f(.7f, .7f, .7f, 1f);
        GL11.glEnable(GL11.GL_TEXTURE_2D);

        //Draw.rect(1000, 10);
        /*
        GL11.glRotatef(-90, 1, 0, 0);
        Draw.renderPlane(10000, 10000,
        10,10,
        100,100);

         */
        GL11.glPopMatrix();



        GL11.glDisable(GL11.GL_LIGHTING);

        GL20.glUseProgram(0);
        ground.render();

        renderClouds();
        skyBox();

        GL20.glUseProgram(toonShader);
        render(SINGLE_RENDER);
    }

    private void renderQuad() {
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

        origin();

        //upper left
        frontCam.camera();
        lights();
        render(QUAD_RENDER);
       
        //upper right
        rightCam.camera();
        lights();
        render(QUAD_RENDER);
        
        //lower left
        topCam.camera();
        lights();
        render(QUAD_RENDER);
        
        //lower right
        perCam.camera();
        lights();
        render(QUAD_RENDER);



    }

    //THIS METHOD ASSUMES THE CAMERA IS IN SCREEN SPACE MODE
    private void renderMouseHelp() {

        if (lMouseX != -1) {
            if (mode == BOX_SELECT) {

                GL11.glColor3f(1, 1, 1);
                GL11.glBegin(GL11.GL_LINE_LOOP);
                GL11.glVertex3f(lMouseX, lMouseY, 0);
                GL11.glVertex3f(lMouseX, mouseY, 0);
                GL11.glVertex3f(mouseX, mouseY, 0);
                GL11.glVertex3f(mouseX, lMouseY, 0);
                GL11.glEnd();
            } else if (mode == CREATE_LINE) {

                /*
                GL11.glColor3f(1, 1, 1);
                GL11.glBegin(GL11.GL_LINES);
                GL11.glVertex3f(lMouseX, lMouseY, 0);
                GL11.glVertex3f(mouseX, mouseY, 0);
                GL11.glEnd();
                 */
            }

        }
    }

    private void renderClouds() {

        Cloud temp;
        for (int i = 0; i < cloudLayer.length; i++) {
            temp = cloudLayer[i];
            GL11.glPushMatrix();
            GL11.glTranslatef(temp.x, temp.y, temp.z);
            GL11.glScalef(temp.scale, temp.scale, temp.scale);
            cloud.draw();
            GL11.glPopMatrix();
        }
    }

    private int loadALData() {
        // Load wav data into a buffer.
        AL10.alGenBuffers(soundBuffer);

        if (AL10.alGetError() != AL10.AL_NO_ERROR) {
            return AL10.AL_FALSE;
        }

        WaveData waveFile = WaveData.create("FancyPants.wav");
        AL10.alBufferData(buffer.get(0), waveFile.format, waveFile.data, waveFile.samplerate);
        waveFile.dispose();
        return 0;

    }

    private void initGUI() {
        Font font = new Font("Courier New", Font.PLAIN, 12);
        trueTypeFont = new TrueTypeFont(font, true);

        gui = new GUI(this, trueTypeFont);
        VerticalLayout createMenu = new VerticalLayout(gui, 0, 0, 150, "");
        createMenu.add(new Button("Avionics", this));
        createMenu.add(new Button("Camera", this));
        createMenu.add(new Button("Cannon", this));
        createMenu.add(new Button("Coupling", this));
        createMenu.add(new Button("Controller", this));
        createMenu.add(new Button("Engine", this));
        createMenu.add(new Button("Explosive", this));
        createMenu.add(new Button("Line", this));
        createMenu.add(new Button("Midpoint", this));
        createMenu.add(new Button("Point", this));
        createMenu.add(new Button("Smoke", this));
        createMenu.add(new Button("Wing", this));
        createMenu.add(new Button("Wheel", this));

        VerticalLayout modifyMenu = new VerticalLayout(gui, 0, 0, 150, "");
        modifyMenu.add(new Button("Extrude", this));
        modifyMenu.add(new Button("Helper", this));
        modifyMenu.add(new Button("Link", this));

        modifyMenu.add(new Button("Scale", this));

        VerticalLayout selectMenu = new VerticalLayout(gui, 0, 0, 150, "");
        selectMenu.add(new Button("Box Select", this));
        selectMenu.add(new Button("Select Point", this));
        selectMenu.add(new Button("Select Wing", this));
        selectMenu.add(new Button("Select Line", this));

        VerticalLayout wiringMenu = new VerticalLayout(gui, 0, 0, 150, "");
        wiringMenu.add(new Button("Constant", this));
        wiringMenu.add(new Button("Joystick Axis", this));
        wiringMenu.add(new Button("Joystick Button", this));
        wiringMenu.add(new Button("Inverter", this));

        wiringMenu.add(new Button("Wire", this));

        probeMenu = new VerticalLayout(gui, 0, 0, 150, "");

        wiringMenu.add(new Menu("Probe", probeMenu));

        VerticalLayout missionMenu = new VerticalLayout(gui, 0, 0, 150, "");
        missionMenu.add(new Button("Destruct", this));
        missionMenu.add(new Button("Proximity", this));
        missionMenu.add(new Button("Sequence", this));

        gui.setRightPopup(dataBlock = new VerticalLayout(gui, 400, 400, 150, ""));
        dataBlock.add(new Menu("Create", createMenu));
        dataBlock.add(new Menu("Select", selectMenu));
        dataBlock.add(new Menu("Mission", missionMenu));
        dataBlock.add(new Menu("Modify", modifyMenu));
        dataBlock.add(new Menu("Wiring", wiringMenu));

        dataBlock.add(new Button("Reset", this));

        //clear out the wiring gui
        wiringGUI = new GUI(this, trueTypeFont);
        wiringLayout = new AbsoluteLayout(wiringGUI);
        wiringGUI.add(wiringLayout);

        infoBox = new VerticalLayout(gui, 100, 800, 150, "Info");
        gui.add(infoBox);
    }

    public void registerMouseEvent(GUI gui) {
    }

    private void handleWiring() {

        if (wiring.size() > 0) {
            for (WiringNode node : wiring) {
                node.invalidate();
            }


            boolean exit = false;


            //nodes which have dependencies will ignore the evaluate command
            //until the next cycle. Loop until every node is donde.
            while (exit == false) {
                exit = true;
                for (WiringNode node : wiring) {
                    if (!node.finished) {
                        node.evaluate();
                        if (!node.finished) {
                            exit = false;
                        }
                    }
                }
            }

        }
    }

    private void handleKeyboard() {


        if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
            // Exit if Escape is pressed
            done = true;
        }

        if (Display.isCloseRequested()) {
            // Exit if window is closed
            done = true;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_F1) && !f1) {
            // Tell Program F1 Is Being Held
            f1 = true; // Tell Program F1 Is Being Held
            mode = CONNECT;
        }
        if (!Keyboard.isKeyDown(Keyboard.KEY_F1)) {
            // Is F1 Being Pressed?
            f1 = false;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_SPACE) && !space) {
            space = true;
            running = 1 - running;
        }
        if (!Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
            space = false;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) && !shift) {
            shift = true;

        }
        if (!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
            shift = false;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) && !ctrl) {
            ctrl = true;

        } else if (!Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
            ctrl = false;
        } else if (!Keyboard.isKeyDown(Keyboard.KEY_E)) {
            ekey = false;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_W) && !wkey) {
            wkey = true;

            String out = pickle();
            writeFile(new File("c:\\temp\\out.txt"), out);
        } else if (!Keyboard.isKeyDown(Keyboard.KEY_E)) {
            wkey = false;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_R) && !rkey) {
            rkey = true;
            readString(readFile(new File("c:\\temp\\out.txt")));
        } else if (!Keyboard.isKeyDown(Keyboard.KEY_R)) {
            rkey = false;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
            mode = SELECT;
            clearSelected();
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_RBRACKET)) {
            for (Selectable n : selected) {
                ((Point) n).adjustMass(.05f);
            }
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_LBRACKET)) {
            for (Selectable n : selected) {
                ((Point) n).adjustMass(-.05f);
            }
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_O)) {
            if (selected.size() > 0) {
                perCam.setLookAt(selected.elementAt(0));
                chaseCam.setLookAt(selected.elementAt(0));
            }
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_F1)) {
            viewMode = QUAD_VIEW;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_F2)) {
            viewMode = PERSPECTIVE_VIEW;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_F3)) {
            viewMode = WIRING;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_F4)) {
            viewMode = FLIGHT_CAM;
        }

        if (Keyboard.isKeyDown(Keyboard.KEY_T)) {
            mode = SELECT_LINE;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_M)) {
            mode = MOVE_POINTS;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_L)) {
            createLattice();
        }

        if (!Keyboard.isKeyDown(Keyboard.KEY_J)) {
            jkey = false;
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_DELETE)) {
            deleteSelected();
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)
                && Keyboard.isKeyDown(Keyboard.KEY_Z)
                && !zkey) {
            zkey = true;
            popUndo();
        }
        if (!Keyboard.isKeyDown(Keyboard.KEY_Z)) {
            zkey = false;
        }

    }

    private void deleteSelected() {
        for (Selectable s : selected) {
            if (s instanceof LineConstraint) {
                LineConstraint l = (LineConstraint) s;
                constraints.remove(l);
            } else if (s instanceof Point) {
                Point p = (Point) s;
                nodes.remove(p);
                //things may hang on points.
                //So, we need to go through every constraint
                //and see if they depend on that point

                for (Node n : constraints) {
                    if (n.dependsOn(p)) {
                        toRemove.add(n);

                    }
                }
                for (Node n : nodes) {
                    if (n.dependsOn(p)) {
                        toRemove.add(n);
                    }
                }

                //you have to remove these later to avoid modifying a loop
                //while iterating through it... comodification says the error
                constraints.removeAll(toRemove);
                nodes.removeAll(toRemove);

            }
        }
    }

    private void initCameras() {

        frontCam = new OrthoCamera(1.f * windowWidth / windowHeight, 0, windowWidth / 2, 0, 0, ZDIST, 0, windowHeight / 2, windowWidth / 2, windowHeight / 2);
        rightCam = new OrthoCamera(1.f * windowWidth / windowHeight, 1, windowWidth / 2, ZDIST, 0, 0, windowWidth / 2, windowHeight / 2, windowWidth / 2, windowHeight / 2);
        topCam = new OrthoCamera(1.f * windowWidth / windowHeight, 2, windowWidth / 2, 0, ZDIST, .000001f, 0, 0, windowWidth / 2, windowHeight / 2);
        perCam = new PerspectiveCamera(30, 10, 30, windowWidth / 2, 0, windowWidth / 2, windowHeight / 2);
        chaseCam = new PerspectiveCamera(100, 0, 0, 0, 0, windowWidth, windowHeight);
    }

    private void mouseHover(int mouseX, int mouseY) {
        //determine which window the mouse is over
        //right

        if (viewMode == QUAD_VIEW) {
            if (mouseX > windowWidth / 2) {
                //up
                if (mouseY > windowHeight / 2) {
                    activeWindow = rightCam;
                } else {
                    activeWindow = perCam;
                }
            } else {
                //up
                if (mouseY > windowHeight / 2) {
                    activeWindow = frontCam;
                } else {
                    activeWindow = topCam;
                }
            }
        } else {
            activeWindow = chaseCam;
        }

    }

    private boolean render(int renderMode) {


        if (viewMode == QUAD_VIEW){
        GL20.glUseProgram(basicShader);
        origin();
        }

        for (Node node : nodes) {

            node.render(renderMode);
        }
        for (Node node : constraints) {
            node.render(renderMode);
        }
        if (shadows) {
            GL20.glUseProgram(shadowShader);
            for (Node node : constraints) {
                node.render(renderMode);
            }
            GL20.glUseProgram(basicShader);
        }

        if (viewMode == QUAD_VIEW){
        GL20.glUseProgram(basicShader);
        workingPlane();
        }

        if (running != 1) {

            calcCOG();
        }

        return true;
    }

    private void createWindow() throws Exception {
        Display.setFullscreen(fullscreen);


        DisplayMode d[] = Display.getAvailableDisplayModes();

        for (int i = 0; i < d.length; i++) {

            if (d[i].getWidth() == windowWidth && d[i].getHeight() == windowHeight && d[i].getBitsPerPixel() == 32) {
                displayMode = d[i];
                break;
            }
        }

        Display.setDisplayMode(displayMode);
        Display.create();
        Display.setVSyncEnabled(true);

    }

    private void buildShaders() {

        //vertex shaders
        int passThrough = Util.buildShader("flightlifter/shaders/PassThrough.vert", GL20.GL_VERTEX_SHADER);
        int flatten = Util.buildShader("flightlifter/shaders/Flatten.vert", GL20.GL_VERTEX_SHADER);
        int textureV = Util.buildShader("flightlifter/shaders/texture.vert", GL20.GL_VERTEX_SHADER);
        int toonV = Util.buildShader("flightlifter/shaders/toon.vert", GL20.GL_VERTEX_SHADER);

        //fragment shaders

        int solidColor = Util.buildShader("flightlifter/shaders/SolidColor.frag", GL20.GL_FRAGMENT_SHADER);
        int textureS = Util.buildShader("flightlifter/shaders/Texture.frag", GL20.GL_FRAGMENT_SHADER);
        int toonS = Util.buildShader("flightlifter/shaders/Toon.frag", GL20.GL_FRAGMENT_SHADER);


        basicShader = Util.buildProgram(passThrough, solidColor);
        shadowShader = Util.buildProgram(flatten, solidColor);
        textureShader = Util.buildProgram(textureV, textureS);
        toonShader = Util.buildProgram(toonV, toonS);



    }



    public void GUIEventPerformed(GUIEvent e) {
        System.out.println("Action:" + e.title);
        if (e.title.equals("Select Wing")) {
            mode = GRAB_TEXTURE;
        } else if (e.title.equals("Scale")) {
            if (selected.size() > 2) {
                pushUndo();
                scaleCenter = calcCenter();
                mode = SCALE;
            } else {
                console("Need to select at least 2 points to extrude.");
            }
        } else if (e.title.equals("Extrude")) {
            if (selected.size() > 2) {
                pushUndo();
                extrude();
            } else {
                console("Need to select at least 3 points to extrude.");
            }
        } else if (e.title.equals("Link")) {
            if (selected.size() > 1) {
                pushUndo();
                createLattice();
            } else {
                console("Need to select at least 2 points to link.");
            }
        } else if (e.title.equals("Helper")) {
            if (selected.size() > 2) {
                pushUndo();
                createHelper();
            } else {
                console("Need to select at least 3 points to make helper.");
            }
        }
        else if (e.title.equals("Select Point")) {
            mode = SELECT;
            clearSelected();
        } else if (e.title.equals("Box Select")) {
            mode = BOX_SELECT;
            clearSelected();
        } else if (e.title.equals("Smoke")) {
            mode = CREATE_SMOKE;
        } else if (e.title.equals("Reset")) {
            reset();
        } else if (e.title.equals("Controller")) {
            mode = CREATE_CONTROLLER;
            clearSelected();
        } else if (e.title.equals("Coupling")) {
            mode = CREATE_COUPLING;
            clearSelected();
        } else if (e.title.equals("Wing")) {
            mode = CREATE_WING;
            clearSelected();
        } else if (e.title.equals("Wheel")) {
            mode = CREATE_WHEEL;
            clearSelected();
        } else if (e.title.equals("Engine")) {
            mode = CREATE_ENGINE;
            clearSelected();
        } else if (e.title.equals("Explosive")) {
            mode = CREATE_EXPLOSIVE;
            clearSelected();
        } else if (e.title.equals("Point")) {
            mode = POINT;
        } else if (e.title.equals("Midpoint")) {
            mode = CREATE_MIDPOINT;
            clearSelected();
        } else if (e.title.equals("Line")) {
            mode = CREATE_LINE;
            clearSelected();
        } else if (e.title.equals("Wire")) {
            mode = CONNECT_WIRES;
            console("Mode: Create Wires");
            clearSelected();
        } else if (e.title.equals("Cannon")) {
            mode = CREATE_CANNON;
        } else if (e.title.equals("Camera")) {
            mode = CREATE_CAM;
        } else if (e.title.equals("Constant")) {
            wiringLayout.add(new ConstantNode(100, 100, trueTypeFont));
            wiringLayout.add(new Button("Constant", null));
        } else if (e.title.equals("Joystick Button")) {
            mode = CAPTURE_BUTTON;
        } else if (e.title.equals("Joystick Axis")) {
            mode = CAPTURE_AXIS;
            /*
            for (int i = 0; i < 4; i++) {
                WiringNode temp = new JoystickAxis(i, playerControl, trueTypeFont, selected, 100, 100);
                wiringLayout.add(temp);
                wiring.add(temp);
                mode = SELECT;
            }

             */


        } else if (e.title.equals("Inverter")) {


            WiringNode temp = new InverterNode(200, 200, trueTypeFont, selected);
            wiringLayout.add(temp);
            wiring.add(temp);
            mode = SELECT;

        } else if (e.title.equals("Proximity")) {
            mode = CREATE_PROXIMITY_OBJECTIVE;
        } else if (e.title.equals("Avionics")) {
            mode = CREATE_AVIONICS;
        } //TODO this is not an elegant way of doing this....
        //if it is not one of the above phrases, it's an observable
        else {
            WiringNode temp;
            wiringLayout.add(temp = new Probe(e.generator, e.index, trueTypeFont));
            wiring.add(temp);
        }




    }

    private void initGL() {

        buildShaders();



        cloudLayer = new Cloud[numClouds];

        for (int i = 0; i < numClouds; i++) {
            cloudLayer[i] = new Cloud((float) (-WORLD_RADIUS / 2 + WORLD_RADIUS * Math.random()),
                    2000.f + (float) (2000.f * Math.random()),
                    (float) (-WORLD_RADIUS / 2 + WORLD_RADIUS * Math.random()),
                    (float) (100 + 100.f * Math.random()));
        }


        engineMesh.openFile("flightlifter/meshes/Engine.mra");
        wheelMesh.openFile("flightlifter/meshes/Wheel.mra");
        cannonMesh.openFile("flightlifter/meshes/Cannon.mra");
        fanBlades.openFile("flightlifter/meshes/FanBlades.mra");
        cloud.openFile("flightlifter/meshes/Cloud.mra");




        try {
            tex = texLoader.getTexture("flightLifter/testPattern512.jpg");
            //wingTex = texLoader.getTexture("flightLifter/metal.jpg");

        } catch (IOException ex) {
            System.out.println("IOException");
        }



        //GL11.glEnable(GL13.GL_MULTISAMPLE);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);  //define blending factors
        GL11.glShadeModel(GL11.GL_SMOOTH);

        //GL11.glEnable(GL11.GL_NORMALIZE);

        GL11.glLineWidth(1.5f);

        GL11.glClearColor(0.2f, 0.2f, 0.2f, 1.f); // Light Grey background
        GL11.glClearDepth(1.0); // Depth Buffer Setup


        GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables Depth Testing
        GL11.glDepthFunc(GL11.GL_LEQUAL); // The Type Of Depth Testing To Do

        // Really Nice Perspective Calculations
        // GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);

        //make it so that calls to glcolor will write to the current material color
        GL11.glEnable(GL11.GL_LIGHTING);
        GL11.glEnable(GL11.GL_COLOR_MATERIAL);
        GL11.glColorMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT_AND_DIFFUSE);

        GL11.glLightModeli(GL11.GL_LIGHT_MODEL_TWO_SIDE, GL11.GL_TRUE);




    }

    public void lights() {
        // Create a point light (white)
        setLight(GL11.GL_LIGHT0,
                new float[]{1.0f, 1.0f, 1.0f, 1.0f}, // diffuse color
                new float[]{0.0f, 0.0f, 0.0f, 1.0f}, // ambient
                new float[]{0.0f, 0.0f, 0.0f, 1.0f}, // specular
                new float[]{0, 2500.f, 0.0f, 1.0f}); // position
    }

    private void cleanup() {

        Display.destroy();
    }

    private void initControllers() {
        try {
            Controllers.create();
            int numberOfControllers = Controllers.getControllerCount();

            String playerChosenController = "Logitech Cordless RumblePad 2";


            for (int i = 0; i < numberOfControllers; i++) {
                //System.out.println(Controllers.getController(i).getName());
                Controller c = Controllers.getController(i);
                if (c.getName().equals(playerChosenController)) {
                    playerControl = c;
                }
            }
        } catch (LWJGLException lWJGLException) {
            System.out.println("Controller Exception");
        }

    }

    private void physics() {
        for (Node node : nodes) {
            node.physics();
        }

        for (int i = 0; i < CONSTRAINT_ITERATIONS; i++) {
            for (Node node : constraints) {
                node.physics();
            }
        }

        constraints.removeAll(toRemove);
        toRemove.clear();

        nodes.addAll(toAdd);
        toAdd.clear();

    }

    private PVector calcCenter() {
        PVector center = new PVector();
        Point p;

        float cnt = 0;

        for (Selectable node : selected) {
            if (node instanceof Point) {
                p = (Point) node;
                center = PVector.add(p.getCentroid(), center);
                cnt ++;
            }
        }
        center.div(cnt);
        scaleCenter = center;
        return center;


    }

    private void calcCOG() {

        PVector COG = new PVector();
        Point p;
        float totMass = 0;
        for (Node node : nodes) {
            if (node instanceof Point) {
                p = (Point) node;
                COG = PVector.add(PVector.mult(p.getCentroid(), p.mass), COG);
                totMass += p.mass;
            }
        }

        COG.div(totMass);


        //now do the loop again and figure out the moment arm
        PVector moment = new PVector();
        PVector offset;

        for (Node node : nodes) {
            if (node instanceof Point) {
                p = (Point) node;
                offset = PVector.sub(p.pos, COG);

                moment.x += Util.abs(offset.x * p.mass);
                moment.y += Util.abs(offset.y * p.mass);
                moment.z += Util.abs(offset.z * p.mass);

            }
        }


        moment.div(totMass);

        tex.bind();
        GL11.glColor3f(1f, 1f, 1f);

        GL20.glUseProgram(textureShader);
        GL11.glPushMatrix();
        GL11.glTranslatef(COG.x, COG.y, COG.z);
        Draw.renderCube(1, 1);

        GL11.glColor3f(1f, 1f, 0f);
        GL20.glUseProgram(basicShader);

        GL11.glBegin(GL11.GL_LINES);
        GL11.glVertex3f(0, 0, 0);
        GL11.glVertex3f(moment.x, 0, 0);
        GL11.glVertex3f(0, 0, 0);
        GL11.glVertex3f(0, moment.y, 0);
        GL11.glVertex3f(0, 0, 0);
        GL11.glVertex3f(0, 0, moment.z);
        GL11.glEnd();

        GL11.glPopMatrix();



    }

    private void pollMouse() {


        int dWheel = Mouse.getDWheel();
        //get the current mouse state

        lastMouseX = mouseX;
        lastMouseY = mouseY;
        mouseX = Mouse.getX();
        mouseY = Mouse.getY();

        if (mouseX != lastMouseX || mouseY != lastMouseY) {
            gui.mouseMoved(mouseX, mouseY);
            wiringGUI.mouseMoved(mouseX, mouseY);
        }

        mouseHover(mouseX, mouseY);

        if (dWheel != 0) {
            if (ctrl) {
                zPlaneDepth += snapSize * dWheel / 120;
            } else {
                if (dWheel < 0) {
                    activeWindow.zoom(1.2f);
                } else {
                    activeWindow.zoom(.8f);
                }
            }

        }


        //if the mouse is down, and wasn't last time step -- it's a click!
        if (Mouse.isButtonDown(0) || Mouse.isButtonDown(1) || Mouse.isButtonDown(2)) {
            int whichButton = 0;
            if (Mouse.isButtonDown(0)) {
                whichButton = 0;
            } else if (Mouse.isButtonDown(1)) {
                whichButton = 1;
            } else if (Mouse.isButtonDown(2)) {
                whichButton = 2;
            }
            if (mouseDown == false) {
                mouseDown = true;
                //pass the mouseclick to the GUI first.
                //if the gui says they didn't hit any of the components,
                //call the flightlifter method
                if (gui.mousePressed(mouseX, mouseY, whichButton) == 0
                        && wiringGUI.mousePressed(mouseX, mouseY, whichButton) == 0) {
                    mouseClicked(mouseX, mouseY, whichButton);
                }
            } //if the mouse was down last step -- they may be dragging
            else if (mouseDown == true) {
                int mouseDX = Mouse.getDX();
                int mouseDY = Mouse.getDY();
                if (mouseDX != 0 || mouseDY != 0) {
                    mouseDragged(mouseDX, mouseDY);
                }
            }
        } //if the button isn't downn and it was last time, we have release
        else if (mouseDown == true) {
            mouseDown = false;
            mouseReleased();
        }
    }

    private void pushUndo() {
        undoBuffer.add(pickle());
    }

    private void popUndo() {

        reset();
        if (undoBuffer.size() > 0) {
            readString(undoBuffer.lastElement());
            undoBuffer.removeElementAt(undoBuffer.size() - 1);
        }
    }

    private void mouseClicked(int mouseX, int mouseY, int button) {

        gui.mouseClicked(mouseX, mouseY, button);

        Ray pickRay = activeWindow.getRay(mouseX, mouseY);

        if (button == 0) {
            if (mode == POINT) {

                pushUndo();
                PVector newLoc = PVector.add(pickRay.origin, PVector.mult(pickRay.dir, ZDIST - zPlaneDepth));

                //align to grid
                newLoc.x = snap(newLoc.x);
                newLoc.y = snap(newLoc.y);
                newLoc.z = snap(newLoc.z);


                Point tmp = last;
                nodes.add(last = new Point(newLoc));
                clearSelected();
                select(last);

            } else if (mode == CREATE_MIDPOINT) {
                pickLine(pickRay);
                if (selected.size() > 0) {
                    pushUndo();
                    constraints.add(new MidpointConstraint((LineConstraint) selected.elementAt(0), nodes));
                    clearSelected();
                }

            } else if (mode == CREATE_PROXIMITY_OBJECTIVE) {
                pick(pickRay);
                if (selected.size() > 0) {
                    pushUndo();
                    nodes.add(new ProximityObjective((Point) selected.elementAt(0)));
                    clearSelected();
                }
            } else if (mode == CREATE_SMOKE) {
                pick(pickRay);
                if (selected.size() > 0) {
                    pushUndo();
                    nodes.add(new SmokeNode((Point) selected.elementAt(0)));
                    clearSelected();
                }


            } else if (mode == CREATE_EXPLOSIVE) {
                pick(pickRay);
                if (selected.size() > 0) {
                    pushUndo();
                    nodes.add(new Explosive((Point) selected.elementAt(0), nodes, constraints, toRemove));
                    clearSelected();
                }


            } else if (mode == SELECT) {
                pick(pickRay);

            } else if (mode == SELECT_LINE) {
                pickLine(pickRay);

            } else if (mode == BOX_SELECT) {

                if (lMouseX == -1) {

                    lMouseX = mouseX;
                    lMouseY = mouseY;
                } else {

                    boxSelect(lMouseX, mouseX, lMouseY, mouseY);
                    lMouseX = -1;
                    lMouseY = -1;
                }

            } else if (mode == CREATE_LINE) {

                pick(pickRay);
                if (selected.size() == 2) {
                    pushUndo();
                    constraints.add(new LineConstraint((Point) selected.elementAt(0), (Point) selected.elementAt(1)));
                    deselect(selected.elementAt(0));

                }

            } else if (mode == CREATE_COUPLING) {

                pick(pickRay);
                if (selected.size() == 2) {
                    pushUndo();
                    constraints.add(new Coupling((Point) selected.elementAt(0), (Point) selected.elementAt(1)));
                    clearSelected();

                }

            } else if (mode == CREATE_CONTROLLER) {

                pick(pickRay);
                if (selected.size() == 2) {
                    pushUndo();
                    constraints.add(new ControllerConstraint((Point) selected.elementAt(0), (Point) selected.elementAt(1)));
                    clearSelected();

                }
            } else if (mode == CREATE_ENGINE) {

                pick(pickRay);
                if (selected.size() == 3) {
                    pushUndo();
                    nodes.add(new LineEngine(new OrthoNormalBasis((Point) selected.elementAt(0),
                            (Point) selected.elementAt(1),
                            (Point) selected.elementAt(2)), new MeshLoader[]{engineMesh, fanBlades}));
                    clearSelected();

                }

            } else if (mode == CREATE_WHEEL) {

                pick(pickRay);
                if (selected.size() == 3) {
                    pushUndo();
                    nodes.add(new Wheel(new OrthoNormalBasis((Point) selected.elementAt(0),
                            (Point) selected.elementAt(1),
                            (Point) selected.elementAt(2)), wheelMesh));
                    clearSelected();

                }

            } else if (mode == CREATE_CANNON) {

                pick(pickRay);
                if (selected.size() == 3) {
                    pushUndo();
                    nodes.add(new Cannon(new OrthoNormalBasis((Point) selected.elementAt(0),
                            (Point) selected.elementAt(1),
                            (Point) selected.elementAt(2)),
                            cannonMesh, nodes, toAdd));
                    clearSelected();

                }

            } else if (mode == CREATE_WING) {
                pick(pickRay);
                if (selected.size() == 4) {
                    pushUndo();
                    nodes.add(new Wing((Point) selected.elementAt(0),
                            (Point) selected.elementAt(1),
                            (Point) selected.elementAt(2),
                            (Point) selected.elementAt(3),
                            wingTex, textureShader));
                    clearSelected();

                }
            } else if (mode == CONNECT_WIRES) {

                pickAnyNode(mouseX, mouseY);




            } else if (mode == CREATE_CAM) {
                pick(pickRay);
                if (selected.size() == 3) {
                    pushUndo();
                    nodes.add(flightCam = new FlightCam((Point) selected.elementAt(0),
                            (Point) selected.elementAt(1),
                            (Point) selected.elementAt(2),
                            windowWidth, windowHeight));
                    clearSelected();

                }

            } else if (mode == CREATE_AVIONICS) {
                pick(pickRay);
                if (selected.size() == 3) {
                    pushUndo();
                    OrthoNormalBasis ONB = new OrthoNormalBasis((Point) selected.elementAt(0),
                            (Point) selected.elementAt(1),
                            (Point) selected.elementAt(2));

                    nodes.add(new Avionics(ONB, probeMenu, this));
                    clearSelected();

                }

            }


        }

    }

    private void console(String line) {
        debug += line + "\n";
    }

    private void mouseDragged(int mouseDX, int mouseDY) {

        gui.mouseDragged(mouseDX, mouseDY);
        wiringGUI.mouseDragged(mouseDX, mouseDY);

        if (mode == MOVE_POINTS && Mouse.isButtonDown(0)) {
            pushUndo();
            for (Selectable s : selected) {

                if (s instanceof Point) {
                    
                    Point p = (Point) s;

                    p.move(PVector.mult(activeWindow.getUp(), mouseDY / 10.f));
                    p.move(PVector.mult(activeWindow.getRight(), mouseDX / 10.f));
                }
            }
        } else if (mode == SCALE && Mouse.isButtonDown(0)) {
            pushUndo();
            PVector dir;
            Point p;
            for (Selectable s : selected) {
                if (s instanceof Point) {
                    p = (Point)s;
                    dir = PVector.sub(p.pos,scaleCenter);
                    dir.normalize();
                    dir.mult( mouseDY / 10.f);
                    
                    p.move(dir);
                    /*
                    tex.bind();
        GL11.glColor3f(1f, 1f, 1f);

        GL20.glUseProgram(textureShader);
        GL11.glPushMatrix();
        GL11.glTranslatef(scaleCenter.x, scaleCenter.y, scaleCenter.z);
        Draw.renderCube(1, 1);

        GL11.glColor3f(1f, 1f, 0f);
        GL20.glUseProgram(basicShader);


        GL11.glPopMatrix();

*/


                }
            }
        }
        else if (mode == GRAB_TEXTURE && Mouse.isButtonDown(0)) {
            for (Selectable s : selected) {

                if (s instanceof Wing) {
                    pushUndo();
                    Wing w = (Wing) s;

                    w.moveTexture(mouseDX / 10.f,
                            mouseDY / 10.f);
                }
            }
        } //middle mouse dragging
        else if (Mouse.isButtonDown(2)) {
            //if they drag with the shift down, that means translate, otherwise
            //orbit
            if (shift) {
                activeWindow.translate(mouseDX, mouseDY);
            } else if (activeWindow instanceof PerspectiveCamera) {
                ((PerspectiveCamera) activeWindow).orbit(mouseDX, mouseDY);

            }

        }
    }

    private void deselect(Selectable n) {
        n.setSelected(0);
        selected.remove(n);
    }

    private void select(Selectable n) {
        if (n.getSelected() != 1) {
            selected.add(n);
            n.setSelected(1);
            if (n instanceof Node) {
                ((Node) n).populateInfoBox(infoBox);
            }
        } else {
            deselect(n);
        }

    }

    private void clearSelected() {
        for (Selectable n : selected) {
            n.setSelected(0);
        }
        selected.clear();
    }

    private void workingPlane() {

        GL11.glPushMatrix();
        //GL11.glEnable(GL11.GL_BLEND);
        //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        //GL11.glDisable(GL11.GL_TEXTURE_2D);
        if (activeWindow == topCam) {
            GL11.glRotatef(-90, 1, 0, 0);

        } else if (activeWindow == rightCam) {
            GL11.glRotatef(-90, 0, 1, 0);
        }

        if (activeWindow != perCam) {

            GL11.glTranslatef(0, 0, zPlaneDepth);

            Draw.gridRect(30);
        }

        GL11.glPopMatrix();



    }

    private void origin() {


        // GL11.glLineWidth(1.5f);
        GL11.glColor3f(.5f, .5f, .5f);

        GL11.glBegin(GL11.GL_LINES);        // Drawing Using Triangles

        GL11.glVertex3f(0.0f, 0f, 0.0f);         // Move Up One Unit From Center (Top Point)
        GL11.glVertex3f(20f, 0f, 0.0f);         // Left And Down One Unit (Bottom Left)

        GL11.glVertex3f(0.0f, 0f, 0.0f);         // Move Up One Unit From Center (Top Point)
        GL11.glVertex3f(0f, 5f, 0.0f);         // Left And Down One Unit (Bottom Left)

        GL11.glVertex3f(0.0f, 0f, 0.0f);         // Move Up One Unit From Center (Top Point)
        GL11.glVertex3f(0f, 0f, 10.0f);         // Left And Down One Unit (Bottom Left)

        GL11.glEnd();
    }

    private void boxSelect(int minX, int maxX, int minY, int maxY) {


        //We'll be transforming all the points to screen space,
        //so make sure you have the right transform loaded.
        //problematic in quad view mode

        activeWindow.camera();

        int f;

        if (maxX < minX) {
            f = minX;
            minX = maxX;
            maxX = f;
        }
        if (maxY < minY) {
            f = minY;
            minY = maxY;
            maxY = f;
        }

        PVector tmp;
        Point p;
        //check for the closest node
        for (Node n : nodes) {
            if (n instanceof Point) {
                tmp = n.getScreenSpaceCentroid();
                if (tmp.x < maxX && tmp.x > minX) {
                    if (tmp.y < maxY && tmp.y > minY) {
                        select(n);
                    }
                }
            }
        }

    }

    private float pickAnyNode(int mouseX, int mouseY) {


        //make sure we have the right transform loaded
        //GLProject.getMatrices();


        PVector mouse = new PVector(mouseX, mouseY);



        float minDist = 999999999.f;


        float lastDist;

        Selectable closest = null;


        //check for the closest node


        for (Node n : nodes) {
            lastDist = PVector.dist(mouse, n.getScreenSpaceCentroid());



            if (lastDist < minDist) {
                closest = n;
                minDist = lastDist;


            }
        }



        //then constraints
        for (Node n : constraints) {
            if (n instanceof ControllerConstraint || n instanceof LineEngine
                    || n instanceof Cannon
                    || n instanceof Coupling) {
                lastDist = PVector.dist(mouse, n.getScreenSpaceCentroid());



                if (lastDist < minDist) {
                    closest = n;
                    minDist = lastDist;


                }
            }
        }
        //then wires
        //remember that these guys work in screen space!



        for (WiringNode n : wiring) {
            lastDist = PVector.dist(mouse, n.getScreenSpaceCentroid());



            if (lastDist < minDist) {
                closest = n;
                minDist = lastDist;


            }
        }

        select(closest);
        System.out.println("Closest: " + closest);


        return minDist;



    }

    private float pick(Ray pickRay) {

        float minDist = 999999999.f;


        float lastDist;

        Node closest = null;



        for (Node n : nodes) {
            lastDist = pickRay.distToPoint(n.getCentroid());



            if (lastDist < minDist) {
                closest = n;
                minDist = lastDist;


            }
        }
        select(closest);


        return minDist;


    }

    private float pickLine(Ray pickRay) {

        float minDist = 999999999.f;


        float lastDist;

        Node closest = null;


        for (Node n : constraints) {
            lastDist = pickRay.distToPoint(n.getCentroid());
            if (lastDist < minDist) {
                closest = n;
                minDist = lastDist;
            }
        }
        System.out.println(closest);

        select(closest);


        return minDist;


    }

    private float pickWireNode(Ray pickRay) {

        float minDist = 999999999.f;


        float lastDist;

        WiringNode closest = null;



        for (WiringNode n : wiring) {
            lastDist = pickRay.distToPoint(n.getCentroid());



            if (lastDist < minDist) {
                closest = n;
                minDist = lastDist;


            }
        }
        select(closest);


        return minDist;



    }

    private float snap(float num) {
        return (Math.round(num / snapSize)) * snapSize;


    }

    private String pickle() {
        StringBuffer out = new StringBuffer();


        for (Node n : nodes) {
            out.append(n.pickle(nodes) + "\n");


        }

        for (Node n : constraints) {
            out.append(n.pickle(nodes) + "\n");


        }

        for (WiringNode n : wiring) {
            out.append(n.pickle(nodes, constraints, wiring) + "\n");


        }

        return out.toString();


    }

    public boolean linkIfUnique(LineConstraint a) {
        boolean unique = true;

        LineConstraint l;
        for (Node n : constraints) {
            l = (LineConstraint) n;
            if (l.isSimilar(a.a, a.b)) {
                unique = false;
            }
        }


        if (unique) {
            constraints.add(a);
        }
        return unique;
    }

    private void createHelper(){

        PVector a = ((Point) selected.elementAt(0)).pos;
        PVector b = ((Point) selected.elementAt(1)).pos;
        PVector c = ((Point) selected.elementAt(2)).pos;
        PVector edge1 = PVector.sub(b, a);
        PVector edge2 = PVector.sub(b, c);
        PVector dir = new PVector();
        PVector.cross(edge1, edge2, dir);
        dir.normalize();

        float area = edge1.mag() + edge2.mag();

        area /= area;
        //direction to extrude helper
        dir.mult(area/2);

        //where to extrude from
        calcCenter();

        scaleCenter.add(dir);
        Point tmp = new Point(scaleCenter.get());
        nodes.add(tmp);

        select(tmp);
        createLattice();


    }

    private void createLattice() {
        int cnt = 0;
        Point a, b;
        for (int i = 0; i < selected.size() - 1; i++) {

            a = (Point) selected.elementAt(i);
            for (int j = i + 1; j < selected.size(); j++) {
                b = (Point) selected.elementAt(j);
                if (linkIfUnique(new LineConstraint(a, b))) {
                    cnt++;
                }
            }

        }
        console("Created " + cnt + " links.");
    }

    private void extrude() {
        //must build our extrusion basis of the first 3 vectors in the series
        PVector a = ((Point) selected.elementAt(0)).pos;
        PVector b = ((Point) selected.elementAt(1)).pos;
        PVector c = ((Point) selected.elementAt(2)).pos;
        PVector edge1 = PVector.sub(b, a);
        PVector edge2 = PVector.sub(b, c);
        PVector dir = new PVector();
        PVector.cross(edge1, edge2, dir);
        dir.normalize();

        dir.mult(5);
        Vector<Point> extrudedPoints = new Vector();

        Point p, t;
        PVector n;
        for (Selectable s : selected) {
            if (s instanceof Point) {
                p = (Point) s;

                //create the new point
                PVector.add(dir, p.pos, n = new PVector());
                extrudedPoints.add(t = new Point(n));

                //connect it to its old point
                constraints.add(new LineConstraint(p, t));
            }
        }

        //cap the new extrusion
        Point last = null;
        for (Point pnt : extrudedPoints) {
            if (last != null) {
                constraints.add(new LineConstraint(pnt, last));
            }
            last = pnt;
        }
        constraints.add(new LineConstraint((Point) extrudedPoints.elementAt(0), last));


        //we want the extruded point to be selected so we can jump
        //into a move mode to extrude them the right amount
        clearSelected();
        for (Selectable s : extrudedPoints) {
            select(s);
        }

        
        nodes.addAll(0, extrudedPoints);
        extrudedPoints.clear();
    }

    private void writeFile(File file, String text) {
        try {
            //Create file
            FileWriter fstream = new FileWriter(file);
            BufferedWriter out = new BufferedWriter(fstream);
            out.write(text);
            //Close the output stream
            out.close();



        } catch (Exception e) {//Catch exception if any
            System.err.println("Error: " + e.getMessage());


        }
    }

    public void reset() {
        nodes = new Vector();
        constraints = new Vector();
        wiring = new Vector();

        selected = new Vector();

        //clear out the wiring gui
        wiringGUI = new GUI(this, trueTypeFont);
        wiringLayout = new AbsoluteLayout(wiringGUI);
        wiringGUI.add(wiringLayout);



    }

    private void readString(String input) {
        //Read File Line By Line
        String strLine;
        StringTokenizer st = new StringTokenizer(input);

        Vector<Node> nodesTemp = new Vector();
        Vector<Node> constraintsTemp = new Vector();
        Vector<WiringNode> wiringTemp = new Vector();



        while (st.hasMoreTokens()) {
            strLine = st.nextToken();

            // Print the content on the console
            String[] line = strLine.split(",");

            if (line[0].equals("P")) {
                nodesTemp.add(new Point(line));


            } else if (line[0].equals("A")) {
                nodesTemp.add(new Avionics(line, nodesTemp, probeMenu, this));


            } else if (line[0].equals("L")) {
                constraintsTemp.add(new LineConstraint(line, nodesTemp));


            } else if (line[0].equals("W")) {
                nodesTemp.add(new Wing(line, nodesTemp, wingTex, textureShader));


            } else if (line[0].equals("E")) {
                nodesTemp.add(new LineEngine(line, nodesTemp, new MeshLoader[]{engineMesh, fanBlades}));



            } else if (line[0].equals("C")) {
                constraintsTemp.add(new ControllerConstraint(line, nodesTemp));


            } else if (line[0].equals("J")) {
                WiringNode temp = new JoystickAxis(line, playerControl, trueTypeFont, selected);

                wiringLayout.add(temp);
                wiringTemp.add(temp);


            } else if (line[0].equals("B")) {
                WiringNode temp = new JoystickButton(line, playerControl, trueTypeFont, selected);
                wiringLayout.add(temp);
                wiringTemp.add(temp);


            } else if (line[0].equals("I")) {
                WiringNode temp = new InverterNode(line, trueTypeFont, selected);
                wiringTemp.add(temp);
                wiringLayout.add(temp);


            } else if (line[0].equals("K")) {
                nodesTemp.add(new Cannon(line, nodesTemp, toAdd, cannonMesh));


            } else if (line[0].equals("M")) {
                nodesTemp.add(new MidpointConstraint(line, nodesTemp));


            } else if (line[0].equals("F")) {
                nodesTemp.add(flightCam = new FlightCam(line, nodesTemp, windowWidth, windowHeight));


            } else if (line[0].equals("S")) {
                nodesTemp.add(new SmokeNode(line, nodesTemp));


            } else if (line[0].equals("X")) {
                nodesTemp.add(new Explosive(line, nodesTemp, constraintsTemp, toRemove));


            } else if (line[0].equals("U")) {
                constraintsTemp.add(new Coupling(line, nodesTemp));


            } else if (line[0].equals("p")) {
                WiringNode temp = new Probe(line, nodesTemp, trueTypeFont);
                wiringLayout.add(temp);
                wiringTemp.add(temp);


            }


        }

        for (WiringNode n : wiringTemp) {
            if (n instanceof WiringOutput) {
                WiringOutput w = (WiringOutput) n;
                w.hookUp(nodesTemp, constraintsTemp, wiringTemp);


            }
        }



        //this will help diffuse energy during constraint enforcement
        Collections.shuffle(constraints);

        nodes.addAll(nodesTemp);
        constraints.addAll(constraintsTemp);
        wiring.addAll(wiringTemp);




    }

    public static String readFile(File file) {
        StringBuffer out = new StringBuffer();


        try {
            // Open the file that is the first
            // command line parameter
            FileInputStream fstream = new FileInputStream(file);
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine;



            while ((strLine = br.readLine()) != null) {
                out.append(strLine + "\n");




            } //Close the input stream
            in.close();


        } catch (Exception e) {//Catch exception if any
            e.printStackTrace();


        }
        return out.toString();



    }

    public static void setLight(int GLLightHandle,
            float[] diffuseLightColor,
            float[] ambientLightColor,
            float[] specularLightColor,
            float[] position) {

        FloatBuffer ltDiffuse = allocFloats(diffuseLightColor);
        FloatBuffer ltAmbient = allocFloats(ambientLightColor);
        FloatBuffer ltSpecular = allocFloats(specularLightColor);
        FloatBuffer ltPosition = allocFloats(position);
        GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, ltDiffuse);   // color of the direct illumination
        GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, ltSpecular); // color of the highlight
        GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ltAmbient);   // color of the reflected light
        GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition);
        GL11.glEnable(GLLightHandle);	// Enable the light (GL_LIGHT1 - 7)



    }

    public static FloatBuffer allocFloats(int howmany) {
        return ByteBuffer.allocateDirect(howmany * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();


    }

    public static FloatBuffer allocFloats(float[] floatarray) {
        FloatBuffer fb = ByteBuffer.allocateDirect(floatarray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        fb.put(floatarray).flip();


        return fb;


    }

    public static IntBuffer allocInts(int[] intarray) {
        IntBuffer fb = ByteBuffer.allocateDirect(intarray.length * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
        fb.put(intarray).flip();


        return fb;


    }

    public static ShortBuffer allocShorts(short[] shortarray) {
        ShortBuffer fb = ByteBuffer.allocateDirect(shortarray.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
        fb.put(shortarray).flip();


        return fb;


    }

    public static void sendUniform1i(int id, String name, int value) {
        int location = getUniformLocation(id, name);
        ARBShaderObjects.glUniform1iARB(location, value);


    }

    private static int getUniformLocation(int id, String name) {
        buffer.clear();
        buffer.put(name.getBytes());
        buffer.put((byte) 0);
        buffer.flip();


        return ARBShaderObjects.glGetUniformLocationARB(id, buffer);


    }

    public static void sendUniform3f(int id, String name, float value1, float value2, float value3) {
        int location = getUniformLocation(id, name);


        if (checkGLError(location, name) == 0) {
            return;


        }
        ARBShaderObjects.glUniform3fARB(location, value1, value2, value3);


    }

    private static int checkGLError(int location, String name) {
        if (location == -1) {
            System.out.println("Error: Couldn't locate " + name);


            return 0;


        }
        return 1;


    }

    public void set2DMode() {
        set2DMode(0, screenWidth, 0, screenHeight);


    }

    public void set2DMode(float x, int width, float y, int height) {
        if (viewMode == QUAD_VIEW) {
            GL11.glViewport(0, 0, width, height);


        }
        GL11.glMatrixMode(GL11.GL_PROJECTION);                        // Select The Projection Matrix
        GL11.glLoadIdentity();                                   // Reset The Projection Matrix
        GL11.glOrtho(x, width, y, height, -1, 1);                          // Set Up An Ortho Screen
        GL11.glMatrixMode(GL11.GL_MODELVIEW);                         // Select The Modelview Matrix
        GL11.glLoadIdentity();                                   // Reset The Modelview Matrix


    }

    public void set3DMode() {
        GL11.glMatrixMode(GL11.GL_PROJECTION);                        // Select The Projection Matrix
        GL11.glLoadIdentity();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);                         // Select The Modelview Matrix
        GL11.glLoadIdentity();


    }

    private void skyBox() {


        float sz = WORLD_RADIUS / 2;



        for (int i = 0; i
                < 4; i++) {

            GL11.glBegin(GL11.GL_QUADS);
            GL11.glColor3f(.5f, .5f, .9f);
            GL11.glVertex3f(-sz, sz, -sz);
            GL11.glVertex3f(sz, sz, -sz);

            GL11.glColor3f(.8f, .8f, .9f);
            GL11.glVertex3f(sz, 0, -sz);
            GL11.glVertex3f(-sz, 0, -sz);

            GL11.glEnd();

            GL11.glRotatef(90, 0, 1, 0);


        }

        GL11.glBegin(GL11.GL_QUADS);
        GL11.glColor3f(.5f, .5f, .9f);
        GL11.glVertex3f(-sz, sz, sz);
        GL11.glVertex3f(-sz, sz, -sz);
        GL11.glVertex3f(sz, sz, -sz);
        GL11.glVertex3f(sz, sz, sz);

        GL11.glEnd();




    }

    private void mouseReleased() {
        for (Node n : constraints) {
            if (n instanceof LineConstraint) {
                LineConstraint l = (LineConstraint) n;
                l.validateLength();


            }
        }
    }

    /**
     * call the LWJGL Sphere class to draw sphere geometry
     * with texture coordinates and normals
     * @param facets  number of divisions around longitude and latitude
     */
    public static void renderSphere(int facets) {
        Sphere s = new Sphere();            // an LWJGL class
        s.setOrientation(GLU.GLU_OUTSIDE);  // normals point outwards
        s.setTextureFlag(true);             // generate texture coords
        GL11.glPushMatrix();


        {
            GL11.glRotatef(-90f, 1, 0, 0);    // rotate the sphere to align the axis vertically
            s.draw(1, facets, facets);              // run GL commands to draw sphere


        }
        GL11.glPopMatrix();


    }

    public static void setLightPosition(int GLLightHandle, float x, float y, float z) {
        put(tmpFloats, new float[]{x, y, z, 1});
        GL11.glLight(GLLightHandle, GL11.GL_POSITION, tmpFloats);


    }

    public static void put(FloatBuffer b, float[] values) {
        b.clear();
        b.put(values).flip();

    }
}
